package self

import (
	"database/sql"
	"log"
	"os"

	_ "gitee.com/tinkler/yunfei/pgself"
	"github.com/dgrijalva/jwt-go"
	"github.com/kataras/iris"
	"github.com/kataras/iris/context"
)

const (
	// AuthTypeJWT the jwt auth type
	AuthTypeJWT = iota
)

// AuthType auth type
type authType int

// Options is a configuration container to setup the self middleware.
// Depends on postgres driver
type Options struct {
	AuthType       authType
	AuthKey        []byte
	Debug          bool
	UserModel      interface{}
	DataSourceName string
	RoleACL        bool
}

// PgSelf postgreSQL base self
// new
type PgSelf struct {
	authKey []byte
	pg      *sql.DB
	log     *log.Logger
	claims  *Claims
	ctx     iris.Context
}

// UserClaims jwt custom claims
type UserClaims struct {
	jwt.StandardClaims
	User interface{} `json:"user"`
}

// Claims jwt claims
type Claims struct {
	jwt.StandardClaims
	Username string      `json:"username"`
	Rolename string      `json:"rolename"`
	User     interface{} `json:"user"`
}

// NewSelfFromYMAL create new self from ymal
func NewSelfFromYMAL(path string) *PgSelf {
	conf := GetConfigFromYaml(path)

	pg, err := sql.Open("postgres", GetDatasourceFromConfig(conf))
	if err != nil {
		panic(err)
	}
	iris.RegisterOnInterrupt(func() {
		pg.Close()
	})
	s := PgSelf{authKey: []byte(conf.AuthKey), pg: pg}
	if conf.Debug {
		s.log = log.New(os.Stdout, "[self] ", log.LstdFlags)
	}
	return &s
}

// Serve middleware serve
func (s *PgSelf) Serve(ctx context.Context) {
	if ctx.Method() == iris.MethodOptions {
		return
	}
	// Authorization token
	authT := ctx.GetHeader("Authorization")
	if authT == "" {
		ctx.StatusCode(iris.StatusUnauthorized)
		ctx.Values().Set("message", "Http Header Authorization require!")
		ctx.StopExecution()
		return
	}
	// Parse Authorization token
	token, err := jwt.ParseWithClaims(authT, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return s.authKey, nil
	})
	if err != nil {
		s.logf("parse token %10s... with claims error", authT)
		ctx.StatusCode(iris.StatusUnauthorized)
		ctx.Values().Set("message", "Authorization Token string is invalid!")
		ctx.StopExecution()
		return
	}
	// Trans Claims
	claims, ok := token.Claims.(*Claims)
	if !ok {
		s.logf("claims %v transfer error", token.Claims)
		ctx.StatusCode(iris.StatusInternalServerError)
		ctx.StopExecution()
		return
	}
	// Save user data
	s.claims = claims
	ctx.Next()
}

// GetUser get user info from claims
func (s *PgSelf) GetUser() interface{} {
	return s.claims.User
}

// CreateToken create self token
func (s *PgSelf) CreateToken(claims *Claims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	ss, err := token.SignedString(s.authKey)
	if err != nil {
		return "", err
	}
	return ss, nil
}

func (s *PgSelf) logf(format string, a ...interface{}) {
	if s.log != nil {
		s.log.Printf(format, a...)
	}
}
